// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
	"fmt"

	"net/http"
	"strconv"

	"github.com/vmware/harbor/src/common/dao"
	"github.com/vmware/harbor/src/common/models"
	"github.com/vmware/harbor/src/common/utils/log"
)

// RepPolicyAPI handles /api/replicationPolicies /api/replicationPolicies/:id/enablement
type RepPolicyAPI struct {
	BaseController
}

// Prepare validates whether the user has system admin role
func (pa *RepPolicyAPI) Prepare() {
	pa.BaseController.Prepare()
	if !pa.SecurityCtx.IsAuthenticated() {
		pa.HandleUnauthorized()
		return
	}

	if !pa.SecurityCtx.IsSysAdmin() {
		pa.HandleForbidden(pa.SecurityCtx.GetUsername())
		return
	}
}

// Get ...
func (pa *RepPolicyAPI) Get() {
	id := pa.GetIDFromURL()
	policy, err := dao.GetRepPolicy(id)
	if err != nil {
		log.Errorf("failed to get policy %d: %v", id, err)
		pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if policy == nil {
		pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	pa.Data["json"] = policy
	pa.ServeJSON()
}

// List filters policies by name and project_id, if name and project_id
// are nil, List returns all policies
func (pa *RepPolicyAPI) List() {
	name := pa.GetString("name")
	projectIDStr := pa.GetString("project_id")

	var projectID int64
	var err error

	if len(projectIDStr) != 0 {
		projectID, err = strconv.ParseInt(projectIDStr, 10, 64)
		if err != nil || projectID <= 0 {
			pa.CustomAbort(http.StatusBadRequest, "invalid project ID")
		}
	}

	policies, err := dao.FilterRepPolicies(name, projectID)
	if err != nil {
		log.Errorf("failed to filter policies %s project ID %d: %v", name, projectID, err)
		pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	for _, policy := range policies {
		project, err := pa.ProjectMgr.Get(policy.ProjectID)
		if err != nil {
			pa.HandleInternalServerError(fmt.Sprintf(
				"failed to get project %d: %v", policy.ProjectID, err))
			return
		}
		if project != nil {
			policy.ProjectName = project.Name
		}
	}

	pa.Data["json"] = policies
	pa.ServeJSON()
}

// Post creates a policy, and if it is enbled, the replication will be triggered right now.
func (pa *RepPolicyAPI) Post() {
	policy := &models.RepPolicy{}
	pa.DecodeJSONReqAndValidate(policy)

	/*
		po, err := dao.GetRepPolicyByName(policy.Name)
		if err != nil {
			log.Errorf("failed to get policy %s: %v", policy.Name, err)
			pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}

		if po != nil {
			pa.CustomAbort(http.StatusConflict, "name is already used")
		}
	*/

	project, err := pa.ProjectMgr.Get(policy.ProjectID)
	if err != nil {
		log.Errorf("failed to get project %d: %v", policy.ProjectID, err)
		pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if project == nil {
		pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("project %d does not exist", policy.ProjectID))
	}

	target, err := dao.GetRepTarget(policy.TargetID)
	if err != nil {
		log.Errorf("failed to get target %d: %v", policy.TargetID, err)
		pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if target == nil {
		pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID))
	}

	policies, err := dao.GetRepPolicyByProjectAndTarget(policy.ProjectID, policy.TargetID)
	if err != nil {
		log.Errorf("failed to get policy [project ID: %d,targetID: %d]: %v", policy.ProjectID, policy.TargetID, err)
		pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if len(policies) > 0 {
		pa.CustomAbort(http.StatusConflict, "policy already exists with the same project and target")
	}

	pid, err := dao.AddRepPolicy(*policy)
	if err != nil {
		log.Errorf("Failed to add policy to DB, error: %v", err)
		pa.RenderError(http.StatusInternalServerError, "Internal Error")
		return
	}

	if policy.Enabled == 1 {
		go func() {
			if err := TriggerReplication(pid, "", nil, models.RepOpTransfer); err != nil {
				log.Errorf("failed to trigger replication of %d: %v", pid, err)
			} else {
				log.Infof("replication of %d triggered", pid)
			}
		}()
	}

	pa.Redirect(http.StatusCreated, strconv.FormatInt(pid, 10))
}

// Put modifies name, description, target and enablement of policy
func (pa *RepPolicyAPI) Put() {
	id := pa.GetIDFromURL()
	originalPolicy, err := dao.GetRepPolicy(id)
	if err != nil {
		log.Errorf("failed to get policy %d: %v", id, err)
		pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if originalPolicy == nil {
		pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	policy := &models.RepPolicy{}
	pa.DecodeJSONReq(policy)
	policy.ProjectID = originalPolicy.ProjectID
	pa.Validate(policy)

	/*
		// check duplicate name
		if policy.Name != originalPolicy.Name {
			po, err := dao.GetRepPolicyByName(policy.Name)
			if err != nil {
				log.Errorf("failed to get policy %s: %v", policy.Name, err)
				pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
			}

			if po != nil {
				pa.CustomAbort(http.StatusConflict, "name is already used")
			}
		}
	*/

	if policy.TargetID != originalPolicy.TargetID {
		//target of policy can not be modified when the policy is enabled
		if originalPolicy.Enabled == 1 {
			pa.CustomAbort(http.StatusBadRequest, "target of policy can not be modified when the policy is enabled")
		}

		// check the existance of target
		target, err := dao.GetRepTarget(policy.TargetID)
		if err != nil {
			log.Errorf("failed to get target %d: %v", policy.TargetID, err)
			pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}

		if target == nil {
			pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID))
		}

		// check duplicate policy with the same project and target
		policies, err := dao.GetRepPolicyByProjectAndTarget(policy.ProjectID, policy.TargetID)
		if err != nil {
			log.Errorf("failed to get policy [project ID: %d,targetID: %d]: %v", policy.ProjectID, policy.TargetID, err)
			pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}

		if len(policies) > 0 {
			pa.CustomAbort(http.StatusConflict, "policy already exists with the same project and target")
		}
	}

	policy.ID = id

	/*
		isTargetChanged := !(policy.TargetID == originalPolicy.TargetID)
		isEnablementChanged := !(policy.Enabled == policy.Enabled)

		var shouldStop, shouldTrigger bool

		// if target and enablement are not changed, do nothing
		if !isTargetChanged && !isEnablementChanged {
			shouldStop = false
			shouldTrigger = false
		} else if !isTargetChanged && isEnablementChanged {
			// target is not changed, but enablement is changed
			if policy.Enabled == 0 {
				shouldStop = true
				shouldTrigger = false
			} else {
				shouldStop = false
				shouldTrigger = true
			}
		} else if isTargetChanged && !isEnablementChanged {
			// target is changed, but enablement is not changed
			if policy.Enabled == 0 {
				// enablement is 0, do nothing
				shouldStop = false
				shouldTrigger = false
			} else {
				// enablement is 1, so stop original target's jobs
				// and trigger new target's jobs
				shouldStop = true
				shouldTrigger = true
			}
		} else {
			// both target and enablement are changed

			// enablement: 1 -> 0
			if policy.Enabled == 0 {
				shouldStop = true
				shouldTrigger = false
			} else {
				shouldStop = false
				shouldTrigger = true
			}
		}

		if shouldStop {
			if err := postReplicationAction(id, "stop"); err != nil {
				log.Errorf("failed to stop replication of %d: %v", id, err)
				pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
			}
			log.Infof("replication of %d has been stopped", id)
		}

		if err = dao.UpdateRepPolicy(policy); err != nil {
			log.Errorf("failed to update policy %d: %v", id, err)
			pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}

		if shouldTrigger {
			go func() {
				if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
					log.Errorf("failed to trigger replication of %d: %v", id, err)
				} else {
					log.Infof("replication of %d triggered", id)
				}
			}()
		}
	*/

	if err = dao.UpdateRepPolicy(policy); err != nil {
		log.Errorf("failed to update policy %d: %v", id, err)
		pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if policy.Enabled != originalPolicy.Enabled && policy.Enabled == 1 {
		go func() {
			if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
				log.Errorf("failed to trigger replication of %d: %v", id, err)
			} else {
				log.Infof("replication of %d triggered", id)
			}
		}()
	}
}

type enablementReq struct {
	Enabled int `json:"enabled"`
}

// UpdateEnablement changes the enablement of the policy
func (pa *RepPolicyAPI) UpdateEnablement() {
	id := pa.GetIDFromURL()
	policy, err := dao.GetRepPolicy(id)
	if err != nil {
		log.Errorf("failed to get policy %d: %v", id, err)
		pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if policy == nil {
		pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	e := enablementReq{}
	pa.DecodeJSONReq(&e)
	if e.Enabled != 0 && e.Enabled != 1 {
		pa.RenderError(http.StatusBadRequest, "invalid enabled value")
		return
	}

	if policy.Enabled == e.Enabled {
		return
	}

	if err := dao.UpdateRepPolicyEnablement(id, e.Enabled); err != nil {
		log.Errorf("Failed to update policy enablement in DB, error: %v", err)
		pa.RenderError(http.StatusInternalServerError, "Internal Error")
		return
	}

	if e.Enabled == 1 {
		go func() {
			if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil {
				log.Errorf("failed to trigger replication of %d: %v", id, err)
			} else {
				log.Infof("replication of %d triggered", id)
			}
		}()
	} else {
		go func() {
			if err := postReplicationAction(id, "stop"); err != nil {
				log.Errorf("failed to stop replication of %d: %v", id, err)
			} else {
				log.Infof("try to stop replication of %d", id)
			}
		}()
	}
}

// Delete : policies which are disabled and have no running jobs
// can be deleted
func (pa *RepPolicyAPI) Delete() {
	id := pa.GetIDFromURL()
	policy, err := dao.GetRepPolicy(id)
	if err != nil {
		log.Errorf("failed to get policy %d: %v", id, err)
		pa.CustomAbort(http.StatusInternalServerError, "")
	}

	if policy == nil || policy.Deleted == 1 {
		pa.CustomAbort(http.StatusNotFound, "")
	}

	if policy.Enabled == 1 {
		pa.CustomAbort(http.StatusPreconditionFailed, "plicy is enabled, can not be deleted")
	}

	jobs, err := dao.GetRepJobByPolicy(id)
	if err != nil {
		log.Errorf("failed to get jobs of policy %d: %v", id, err)
		pa.CustomAbort(http.StatusInternalServerError, "")
	}

	for _, job := range jobs {
		if job.Status == models.JobRunning ||
			job.Status == models.JobRetrying ||
			job.Status == models.JobPending {
			pa.CustomAbort(http.StatusPreconditionFailed, "policy has running/retrying/pending jobs, can not be deleted")
		}
	}

	if err = dao.DeleteRepPolicy(id); err != nil {
		log.Errorf("failed to delete policy %d: %v", id, err)
		pa.CustomAbort(http.StatusInternalServerError, "")
	}
}
